package net.sf.rose.jdbc.service;

import java.io.File;
import java.io.FileInputStream;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import javax.sql.DataSource;

import org.apache.commons.dbcp.BasicDataSourceFactory;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import net.sf.rose.boot.BootStrap;
import net.sf.rose.boot.bean.BootStartBean;
import net.sf.rose.boot.bean.BootStrapBean;
import net.sf.rose.boot.bean.DatabaseBean;
import net.sf.rose.file.monitor.FileMonitorReocrd;
import net.sf.rose.file.scan.ClassFilter;
import net.sf.rose.file.scan.PackageScan;
import net.sf.rose.file.util.FileUtils;
import net.sf.rose.jdbc.KeyGenerator;
import net.sf.rose.jdbc.bean.DataAuthorBean;
import net.sf.rose.jdbc.bean.FieldBean;
import net.sf.rose.jdbc.bean.ModuleBean;
import net.sf.rose.jdbc.bean.TableBean;
import net.sf.rose.jdbc.bean.annotation.Table;
import net.sf.rose.jdbc.bean.annotation.View;
import net.sf.rose.jdbc.bean.annotation.Views;
import net.sf.rose.jdbc.dao.BeanDAO;
import net.sf.rose.jdbc.database.Jdbc;
import net.sf.rose.jdbc.query.BeanSQL;
import net.sf.rose.task.RoseTaskBean;
import net.sf.rose.util.ClassUtils;
import net.sf.rose.util.DataUtils;
import net.sf.rose.util.RoseRuntimeException;

/**
 * 类说明：数据库操作元数据定义类
 * <p>
 * 数据库连接池的初始化，支持配置多个数据库连接
 * <p>
 * 数据库表和视图的定义缓存加载
 * <p>
 * 数据库外部SQL文的加载
 */
public class MetaServiceImpl implements MetaService {

	/** 默认操作数据库定义 */
	private DatabaseBean defaultDatabaseBean = null;

	/** 数据库表定义缓存 */
	private HashMap<String, TableBean> ct = new HashMap<>();

	/**
	 * 功能：根据数据库表或视图名称查找对应数据库表的元数据
	 * 
	 * @param tableName - 数据库表或视图名称
	 * @return 数据库表定义元数据类
	 */
	public TableBean getTableBean(String tableName) {
		return ct.get(tableName);
	}

	/**
	 * 功能：根据数据库实体类更新数据库表结构和视图
	 * 
	 * @param service  - 数据库连接服务
	 * @param clazz    - 表定义实体类
	 * @param updateDb - 同时更新数据库中的表定义
	 */
	public void updateTableDefine(Service service, Class<?> clazz, boolean updateDb) {
		// 扫描数据库实体类定义
		List<TableBean> list = scanTableClass(clazz);
		for (TableBean bean : list) {
			updateTableDefine(service, bean, updateDb);
		}
	}

	/**
	 * 功能：更新数据库表结构或视图定义
	 * 
	 * @param service  - 数据库连接服务
	 * @param meta     - 新的表定义
	 * @param updateDb - 同时更新数据库中的表定义
	 */
	public void updateTableDefine(Service service, TableBean meta, boolean updateDb) {
		// 更新数据库表结构与视图
		if (updateDb) {
			updateDatabaseTable(service, meta);
		}
		// 如果数据库表结构中有定义
		BeanDAO dao = new BeanDAO(service);
		BeanSQL query = dao.getQuerySQL();
		if (ct.get(meta.getTableName()) != null) {
			TableBean bean = ct.get(meta.getTableName());
			if (meta.getDescription() != null) {
				bean.setDescription(meta.getDescription());
			}
			if (meta.getClassName() != null) {
				bean.setClassName(meta.getClassName());
			}
			if (meta.getViewSql() != null) {
				bean.setViewSql(meta.getViewSql());
				bean.setView(1);
			}
			if (meta.getModuleName() != null) {
				bean.setModuleName(meta.getModuleName());
			}
			query.setEntityClass(TableBean.class);
			query.createSaveSql(bean);
			dao.update();
			// 删除所有字段定义
			query.createDeleteSql(FieldBean.class, "tableID", bean.getTableID());
			dao.update();
			// 添加所有字段定义
			int size = 1;
			List<FieldBean> list = new ArrayList<>();
			for (FieldBean field : meta.getFields()) {
				field.setTableID(bean.getTableID());
				field.setSequence(size);
				list.add(field);
				size++;
			}
			bean.setFields(list);
			meta.setFields(list);
			query.createBatchInsertSql(list);
			dao.batchUpdate();
			meta = bean;
		}
		// 如果数据库表结构中没有定义
		else {
			if (meta.isView()) {
				meta.setViewSql(service.getSql(meta.getTableName()));
			}
			// 保存数据库表结构定义
			query.createSaveSql(meta);
			dao.update();
			// 保存数据库字段
			int size = 1;
			for (FieldBean field : meta.getFields()) {
				if (field.isPk() && field.getFieldID() == null) {
					field.setFieldID(KeyGenerator.getSystemUUID());
				}
				field.setTableID(meta.getTableID());
				field.setSequence(size);
				size++;
			}
			query.setEntityClass(FieldBean.class);
			query.createBatchInsertSql(meta.getFields());
			dao.batchUpdate();
		}
		service.commit();
		// 初始化表或视图定义的缓存参数
		updateTableBeanCache(service, meta);
		ct.put(meta.getTableName(), meta);
	}

	/**
	 * @param force - 强制加载
	 * @return 0：不需要同步数据库表结构；1：需要同步数据库表结构；
	 */
	public void readConfigFile(File path, boolean force) {
		BootStrapBean strapBean = BootStrap.getBootBean();
		try {
			// 数据库连接配置文件
			File dbFile = new File(path, "db.properties");
			Properties pro = new Properties();
			pro.load(new FileInputStream(dbFile));
			String str = pro.remove("databaseRun").toString();
			// 如果不强制而且配置文件中没有强制要求启动加载
			if (force == false && str.equals("false")) {
				return;
			}
			String databaseName = pro.remove("databaseName").toString();
			String databaseType = pro.remove("databaseType").toString();
			String fileName = path.getName();
			Service.addDataSourceType(fileName, databaseType); // 设置数据库类型
			DataSource dataSource = null;
			str = pro.remove("dataSourceFactory").toString();
			if (str.equals("com.alibaba.druid.pool.DruidDataSourceFactory")) {
				dataSource = DruidDataSourceFactory.createDataSource(pro);
			} else if (str.equals("org.apache.commons.dbcp.BasicDataSourceFactory")) {
				dataSource = BasicDataSourceFactory.createDataSource(pro);
			}
			Service.addDataSource(fileName, dataSource);
			// 数据库连接配置类
			DatabaseBean bean = new DatabaseBean();
			bean.setDatabaseName(databaseName);
			bean.setFileName(fileName);
			bean.setType(databaseType);
			strapBean.initialDatabaseBean(bean);
			// 如果配置文件的文件名与指定的默认数据库一致
			if (fileName.equals(strapBean.getDefaultDatabaseName())) {
				defaultDatabaseBean = bean;
				// 设置默认数据库名称及连接池
				Service.setDefaultDatabaseName(bean.getFileName());
			}
			// 遍历所有外部SQL配置文件
			for (File file : path.listFiles()) {
				// 如果是数据库连接配置文件，则忽略
				if (file.getName().equals("db.properties")) {
					continue;
				}
				// 如果SQL文配置文件类型与数据库类型不一致，则不加载
				if (file.getName().indexOf(databaseType) == -1) {
					continue;
				}
				// 加载配置SQL文
				bean.getResourceSql().load(file);
			}
		} catch (final Exception e) {
			final String mess = "数据库连接池创建失败！" + '\n' + e.getMessage();
			throw new RoseRuntimeException(mess, e);
		}
	}

	/**
	 * 功能：数据库操作初始化准备
	 * <p>
	 * 读取配置文件，包括数据库连接池和外部视图和查询SQL语句
	 * <p>
	 * 加载所有表结构定义
	 * <p>
	 * 根据需要同步数据库表结构和定义
	 * 
	 */
	public void initial() {
		// 读取配置文件
		File path = new File(BootStrap.getResourcePath(), "database");
		for (File file : path.listFiles()) {
			readConfigFile(file, false);
		}
		if (defaultDatabaseBean == null) {
			throw new RoseRuntimeException("没有设置缺省数据库...");
		}
		Service.setTableCache(ct);
		Service service = new Service();
		// 加载所有表结构定义
		loadTableDefine(service);
		// 如果需要更新数据库表结构
		if (BootStrap.getBootBean().isUpdateDatabase()) {
			// 扫描所有数据库实体类定义，并同步更新数据库
			scanAllTableClass(service);
		} else {
			// 如果监控classes文件夹的变化情况
			if (FileUtils.JAVA_CLASS_PATH != null && FileUtils.JAVA_CLASS_PATH.exists()) {
				dynamicTableDefine(service);
			}
		}
		service.close();
	}

	/**
	 * 功能：加载所有表结构定义
	 * 
	 * @param service - 数据库连接服务
	 */
	private void loadTableDefine(Service service) {
		// 从实体类中加载元数据表结构定义
		// 表
		TableBean meta = new TableBean();
		Table table = TableBean.class.getAnnotation(Table.class);
		meta.initialTable(TableBean.class, table);
		ct.put(meta.getTableName(), meta);
		boolean flag = updateDatabaseTable(service, meta) != null ? true : false;
		// 字段
		meta = new TableBean();
		table = FieldBean.class.getAnnotation(Table.class);
		meta.initialTable(FieldBean.class, table);
		ct.put(meta.getTableName(), meta);
		if (flag) {
			updateDatabaseTable(service, meta);
		}
		// 数据库表模块定义表
		meta = new TableBean();
		table = ModuleBean.class.getAnnotation(Table.class);
		meta.initialTable(ModuleBean.class, table);
		ct.put(meta.getTableName(), meta);
		if (flag) {
			updateDatabaseTable(service, meta);
		}
		// 数据权限定义表
		meta = new TableBean();
		table = DataAuthorBean.class.getAnnotation(Table.class);
		meta.initialTable(DataAuthorBean.class, table);
		ct.put(meta.getTableName(), meta);
		if (flag) {
			updateDatabaseTable(service, meta);
		}
		// 开机启动类
		meta = new TableBean();
		table = BootStartBean.class.getAnnotation(Table.class);
		meta.initialTable(BootStartBean.class, table);
		ct.put(meta.getTableName(), meta);
		if (flag) {
			updateDatabaseTable(service, meta);
		}
		// 定时任务
		meta = new TableBean();
		table = RoseTaskBean.class.getAnnotation(Table.class);
		meta.initialTable(RoseTaskBean.class, table);
		ct.put(meta.getTableName(), meta);
		if (flag) {
			updateDatabaseTable(service, meta);
			service.commit();
		}
		// 从数据库中获取数据库表结构定义信息
		BeanDAO dao = new BeanDAO(service);
		BeanSQL query = dao.getQuerySQL();
		// 加载表定义
		query.setEntityClass(TableBean.class);
		query.setSQL("select * from ADMIN_TABLE ORDER BY TABLE_NAME");
		List<TableBean> tlist = dao.list();
		Map<String, TableBean> tMap = new HashMap<>();
		for (TableBean bean : tlist) {
			tMap.put(bean.getTableID(), bean);
		}
		// 加载字段定义
		query.setEntityClass(FieldBean.class);
		query.setSQL("select * from ADMIN_FIELD order by TABLE_ID,SEQUENCE");
		List<FieldBean> flist = dao.list();
		for (FieldBean field : flist) {
			TableBean t = tMap.get(field.getTableID());
			t.addField(field);
		}
		for (TableBean bean : tMap.values()) {
			updateTableBeanCache(service, bean);
			ct.put(bean.getTableName(), bean);
		}
	}

	/**
	 * 功能：扫描所有数据库实体类定义，并同步更新数据库
	 * 
	 * @param service - 数据库连接服务
	 */
	private void scanAllTableClass(Service service) {
		// 扫描包路径，加载所有的表视图定义
		PackageScan ps = new PackageScan();
		ClassFilter cf = new MetaClassFilter();
		List<TableBean> viewList = new ArrayList<>();
		for (Class<?> clazz : ps.scanPackage("com.business,net.sf.rose", cf)) {
			List<TableBean> list = scanTableClass(clazz);
			if (list.size() > 0) {
				TableBean table = list.get(0);
				if (!table.isView()) {
					list.remove(0);
					updateTableDefine(service, table, true); // 更新数据库表结构定义
				}
				viewList.addAll(list);
			}
		}
		// 更新数据库视图定义
		updateViewDefineList(service, viewList);
		// 保存配置文件
		BootStrap.getBootBean().setUpdateDatabase(false);
		BootStrap.getBootBean().save();
	}

	/**
	 * 功能：扫描数据库实体类定义
	 * 
	 * @param clazz - 数据库实体类对象
	 */
	private List<TableBean> scanTableClass(Class<?> clazz) {
		List<TableBean> list = new ArrayList<>();
		Table table = clazz.getAnnotation(Table.class);
		if (table != null) {
			TableBean meta = new TableBean();
			meta.initialTable(clazz, table);
			list.add(meta);
		}
		View view = clazz.getAnnotation(View.class);
		if (view != null) {
			TableBean meta = new TableBean();
			meta.initialView(clazz, view);
			list.add(meta);
		} else {
			Views array = clazz.getAnnotation(Views.class);
			if (array != null) {
				for (View v : array.value()) {
					TableBean meta = new TableBean();
					meta.initialView(clazz, v);
					list.add(meta);
				}
			}
		}
		return list;
	}

	/**
	 * 循环生成数据库视图，包括视图中引用别的视图的情况
	 * 
	 * @param service - 数据库连接服务
	 * @param vList   - 所有视图定义
	 */
	private void updateViewDefineList(Service service, List<TableBean> vList) {
		int index = 0;
		int from = 0;
		String str = null;
		String sql = null;
		boolean flag = false;
		Map<TableBean, List<String>> map = new HashMap<>();
		// 第一批过一遍，如果SQL文中没有引用别的视图就直接生成，否则保留
		for (TableBean view : vList) {
			from = 0;
			flag = false;
			sql = service.getSql(view.getTableName());
			if (sql == null) {
				System.err.println("视图名：" + view.getTableName() + "没有找到定义的SQL语句...");
				continue;
			}
			index = sql.indexOf("_VIEW "); // 查找SQL文中视图视图标记
			if (index != -1) {
				List<String> list = new ArrayList<>();
				map.put(view, list);
				while (index != -1) {
					flag = true;
					str = sql.substring(from, index); // 截取位置之前的SQL文
					from = str.lastIndexOf(' '); // 查找最后一个空格
					str = str.substring(from + 1) + "_VIEW"; // 二者之间的是视图名称
					list.add(str);
					from = index + 4; // 查找起点往后推移
					index = sql.indexOf("_VIEW ", from); // 往后查找视图标记
				}
			}
			// 如果没有找到视图，则直接生成数据库视图
			if (!flag) {
				updateTableDefine(service, view, true);
			}
		}
		// 当前已生成的所有视图
		Map<String, String> viewMap = new HashMap<>();
		for (TableBean view : ct.values()) {
			viewMap.put(view.getTableName(), view.getTableName());
		}
		// 循环处理剩余的视图，直到空掉
		int count = 0;
		while (map.size() > 0) {
			Map<TableBean, List<String>> newMap = new HashMap<>();
			// 遍历未生成的所有视图
			for (TableBean view : map.keySet()) {
				flag = false;
				List<String> list = map.get(view);
				// 遍历视图SQL文中包含引用的所有其它视图
				for (String key : list) {
					// 只要有一个未生成数据库视图，则不能马上生成视图
					if (viewMap.get(key) == null) {
						flag = true;
					}
				}
				if (flag) {
					// 如果不能马上生成视图，则缓存起来
					newMap.put(view, list);
				} else {
					updateTableDefine(service, view, true);
					viewMap.put(view.getTableName(), view.getTableName());
				}
			}
			map = newMap;
			// 如果循环超过100次，则基本意味着出现了死循环
			if (count > 100) {
				System.out.println(map);
				System.err.println("系统启动加载时出现了死循环，即将关闭...");
				System.exit(0);
			} else {
				count++;
			}
		}
	}

	/**
	 * 功能：系统监测到有实体类更新，则自动同步更新数据库表结构
	 * 
	 * @param service - 数据库连接服务
	 */
	private void dynamicTableDefine(Service service) {
		String pathName = FileUtils.JAVA_CLASS_PATH.getAbsolutePath();
		Set<String> list = FileMonitorReocrd.readData();
		for (String str : list) {
			// 如果是实体类文件
			if (str.endsWith(".class")) {
				str = str.substring(pathName.length());
				str = str.substring(1, str.lastIndexOf('.'));
				str = str.replace('\\', '.');
				str = str.replace('/', '.');
				Class<?> clazz = DataUtils.forName(BootStrap.class, str);
				Table table = clazz.getAnnotation(Table.class);
				if (table != null) {
					TableBean meta = new TableBean();
					meta.initialTable(clazz, table);
					// 更新数据库表结构定义
					updateTableDefine(service, meta, true);
				}
				List<TableBean> viewList = new ArrayList<>();
				View view = clazz.getAnnotation(View.class);
				if (view != null) {
					TableBean meta = new TableBean();
					meta.initialView(clazz, view);
					viewList.add(meta);
				} else {
					Views array = clazz.getAnnotation(Views.class);
					if (array != null) {
						for (View v : array.value()) {
							TableBean meta = new TableBean();
							meta.initialView(clazz, v);
							viewList.add(meta);
						}
					}
				}
				// 更新数据库视图定义
				for (TableBean tv : viewList) {
					updateTableDefine(service, tv, true);
				}
			}
		}
	}

	/**
	 * 功能：初始化实体类缓存参数
	 * 
	 * @param service - 数据库连接服务
	 * @param table   - 表结构定义
	 */
	private void updateTableBeanCache(Service service, TableBean bean) {
		bean.initialFieldCache();
		if (bean.isView()) {
			service.setCurrentDatabaseName(bean.getDatabaseName());
			String sql = service.getSql(bean.getTableName());
			if (sql != null) {
				bean.setViewSql(sql);
			}
		} else {
			String sql = null;
			try {
				// 初始化主键最大值
				FieldBean field = bean.getPkField();
				// 如果主键不存在，则不需要初始化主键缓存
				if (field == null || field.getPolicy() == null) {
					return;
				}
				KeyGenerator gen = field.getBeanKeyGenerator();
				if (gen == null) {
					gen = new KeyGenerator();
					gen.setkeyGenerateString(field.getPolicy());
					field.setBeanKeyGenerator(gen);
				}
				// 如果主键不是使用32位随机字符串，则需要初始化主键缓存
				if (!field.getPolicy().equals("UUID")) {
					sql = "select max(" + field.getFieldName() + ") from " + bean.getTableName();
					service.setCurrentDatabaseName(bean.getDatabaseName());
					ResultSet results = service.getConnection().createStatement().executeQuery(sql);
					if (results.next()) {
						final Object obj = results.getObject(1);
						if (obj != null) {
							Object count = gen.initialCount(obj.toString());
							System.out.println("表名(" + bean.getTableName() + ")的主键字段(" + field.getFieldName()
									+ ")初始化主键完成。计数器值=" + count);
						}
					}
				}
			} catch (Exception e) {
				throw ClassUtils.getException("初始化主键失败！sql=" + sql, e);
			}
		}
	}

	/**
	 * 功能：更新数据库表结构或视图
	 * 
	 * @param table   - 表结构定义
	 * @param service - 数据库连接服务
	 */
	private String updateDatabaseTable(Service service, TableBean bean) {
		// 只更新主数据库的表结构
		if (!bean.getDatabaseName().equals(Service.getDefaultDatabaseName())) {
			return null;
		}
		String sql = null;
		try {
			if (!bean.isView()) {
				service.setCurrentDatabaseName(bean.getDatabaseName());
				Jdbc jdbc = service.getJdbcSqlCreator();
				sql = jdbc.getUpdateSQL(service, bean);
				if (sql != null) {
					Statement stmt = service.getConnection().createStatement();
					stmt.execute(sql);
					stmt.close();
					System.out.println("execute sql:\n" + sql);
				}
				return sql;
			} else {
				String key = bean.getTableName();
				service.setCurrentDatabaseName(bean.getDatabaseName());
				sql = service.getSql(key);
				if (sql != null) {
					// 更新数据库视图
					updateDatabaseView(service, key, sql);
				}
			}
			service.commit();
			return sql;
		} catch (SQLException e) {
			sql = "更新数据库表结构时发生未知异常!\n" + sql;
			throw ClassUtils.getException(sql, e);
		} catch (RoseRuntimeException e) {
			throw e;
		} catch (Exception e) {
			throw ClassUtils.getException("更新数据库表结构时发生未知异常！", e);
		}
	}

	/**
	 * 功能：更新数据库视图
	 * 
	 * @param service - 数据库连接服务
	 * @param key     - 视图名称
	 * @param sql     - 视图SQL文
	 */
	private void updateDatabaseView(Service service, String key, String sql) {
		try {
			// 如果是SQL Server数据库
			if (service.getDatabaseType().equals("sqlserver")) {
				String str = "if exists(select * from sys.views where name='" + key + "') ";
				str = str + "DROP view " + key + ";";
				Statement stmt = service.getConnection().createStatement();
				stmt.execute(str);
				stmt.close();
				stmt = service.getConnection().createStatement();
				stmt.execute("CREATE view " + key + " AS " + sql);
				stmt.close();
			} else {
				sql = "CREATE or REPLACE view " + key + " AS " + sql;
				final Statement stmt = service.getConnection().createStatement();
				stmt.execute(sql);
				stmt.close();
			}
			System.out.println("\nexecute sql:" + sql + '\n');
		} catch (Exception e) {
			// 如果视图创建失败，程序照样能启动
			e.printStackTrace();
		}
	}

}
